package com.ruleengine.worker.rulechain;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.common.graph.ImmutableNetwork;
import com.google.common.graph.MutableNetwork;
import com.google.common.graph.Network;
import com.google.common.graph.NetworkBuilder;
import com.ruleengine.db.model.Rule;
import com.ruleengine.db.model.RuleStatus;
import com.ruleengine.db.model.Rulechain;
import com.ruleengine.db.model.Rulerelation;
import com.ruleengine.worker.model.Chain;
import com.ruleengine.util.model.Metadata;
import com.ruleengine.worker.model.Relation;
import com.ruleengine.worker.model.Status;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;

/**
 * @author lianghanwei on 2024/3/17
 */
public class ChainUtil {

    public static final String METADATA_LABEL_KEY_TENANT_ID = "tenantId";
    public static final String METADATA_LABEL_KEY_RULEENGINE_ID = "ruleengineId";
    public static final String METADATA_LABEL_KEY_RULECHAIN_ID = "rulechainId";

    public static Chain buildChain(Rulechain rulechain, List<Rule> rules, List<Rulerelation> rulerelations) {
        List<com.ruleengine.worker.model.Rule> ruleList = rules.stream()
            .map(e -> buildRule(e, rulechain))
            .collect(Collectors.toList());

        Map<String, com.ruleengine.worker.model.Rule> ruleIdRuleMap = new HashMap<>();
        ruleList.forEach(e -> ruleIdRuleMap.put(e.getMetadata().getUid(), e));

        List<com.ruleengine.worker.model.Relation> relations = rulerelations.stream()
            .map(e -> buildRelation(e, ruleIdRuleMap))
            .collect(Collectors.toList());

        Chain.Spec spec = Chain.Spec.builder()
            .rules(ruleList)
            .relations(relations)
            .topology(buildTopology(relations, ruleList))
            .state(Chain.State.valueOf(rulechain.getState().name()))
            .build();

        return Chain.builder()
            .metadata(buildChainMetadata(rulechain))
            .spec(spec)
            .build();
    }

    public static Network<com.ruleengine.worker.model.Rule, Relation> buildTopology(
        List<Relation> relations,
        List<com.ruleengine.worker.model.Rule> rules) {
        MutableNetwork<com.ruleengine.worker.model.Rule, Relation> topology = NetworkBuilder.directed().build();

        rules.forEach(e -> topology.addNode(e));
        relations.forEach(e -> topology.addEdge(e.getSpec().getUpper(), e.getSpec().getDown(), e));

        return ImmutableNetwork.copyOf(topology);
    }

    public static Relation buildRelation(Rulerelation rulerelation,
        Map<String, com.ruleengine.worker.model.Rule> ruleIdRuleMap) {
        Relation.Spec spec = Relation.Spec.builder()
            .upper(ruleIdRuleMap.get(rulerelation.getUpperRuleId()))
            .down(ruleIdRuleMap.get(rulerelation.getDownRuleId()))
            .build();

        return Relation.builder()
            .metadata(buildRelationMetadata(rulerelation))
            .spec(spec)
            .build();
    }

    @SuppressWarnings("unchecked")
    public static com.ruleengine.worker.model.Rule buildRule(Rule rule, Rulechain rulechain) {
        com.ruleengine.worker.model.Rule.Spec spec = com.ruleengine.worker.model.Rule.Spec.builder()
            .type(com.ruleengine.worker.model.Rule.RuleType.valueOf(rule.getType().name()))
            .args(JSONUtil.toBean(rule.getArgs(), Map.class))
            .build();

        return com.ruleengine.worker.model.Rule.builder()
            .metadata(buildRuleMetadata(rule))
            .spec(spec)
            .status(buildRuleStatus(rule))
            .rulechainId(rulechain.getRulechainId())
            .rulechainVersion(rulechain.getResourceVersion())
            .build();
    }

    public static Status buildRuleStatus(Rule rule) {
        Status status = null;
        if (RuleStatus.ERROR == rule.getStatus()) {
            status = Status.builder()
                .phase(Status.Phase.ERROR)
                .build();
        }
        return status;
    }

    public static Metadata buildChainMetadata(Rulechain rulechain) {
        Map<String, String> labels = buildLabels(rulechain.getTenantId(), rulechain.getRuleengineId());

        return Metadata.builder()
            .name(rulechain.getName())
            .uid(rulechain.getRulechainId())
            .resourceVersion(String.valueOf(rulechain.getResourceVersion()))
            .labels(labels)
            .createTime(LocalDateTimeUtil.toEpochMilli(rulechain.getCreateTime()))
            .updateTime(LocalDateTimeUtil.toEpochMilli(rulechain.getUpdateTime()))
            .build();

    }

    public static Metadata buildRelationMetadata(Rulerelation rulerelation) {
        Map<String, String> labels = Map.of(
            METADATA_LABEL_KEY_TENANT_ID, rulerelation.getTenantId(),
            METADATA_LABEL_KEY_RULECHAIN_ID, rulerelation.getRulechainId()
        );

        return Metadata.builder()
            .name(rulerelation.getRulerelationId())
            .uid(rulerelation.getRulerelationId())
            .resourceVersion(String.valueOf(rulerelation.getResourceVersion()))
            .labels(labels)
            .createTime(LocalDateTimeUtil.toEpochMilli(rulerelation.getCreateTime()))
            .updateTime(LocalDateTimeUtil.toEpochMilli(rulerelation.getUpdateTime()))
            .build();
    }

    public static Metadata buildRuleMetadata(Rule rule) {
        Map<String, String> labels = Map.of(
            METADATA_LABEL_KEY_TENANT_ID, rule.getTenantId(),
            METADATA_LABEL_KEY_RULECHAIN_ID, rule.getRulechainId()
        );

        return Metadata.builder()
            .name(rule.getRuleId())
            .uid(rule.getRuleId())
            .resourceVersion(String.valueOf(rule.getResourceVersion()))
            .labels(labels)
            .createTime(LocalDateTimeUtil.toEpochMilli(rule.getCreateTime()))
            .updateTime(LocalDateTimeUtil.toEpochMilli(rule.getUpdateTime()))
            .build();
    }

    public static Map<String, String> buildLabels(String tenantId, String ruleEngineId) {
        return Map.of(
            METADATA_LABEL_KEY_TENANT_ID, tenantId,
            METADATA_LABEL_KEY_RULEENGINE_ID, ruleEngineId
        );
    }
}
